/*
 * i2c.c
 *
 *  Created on: Mar 21, 2011
 *      Author: Vishal Verma
 */

/* A program demonstrating application of AT24C512 as an I2C EEPROM. */

#include <at89c51ed2.h>  //also includes 8052.h and 8051.h
#include <mcs51reg.h>
#include <stdio.h>
#include "i2c.h"
#include "delay.h"

unsigned int pageId;
unsigned int offAddr;

/*
* NAME :            addrInterpret
*
* DESCRIPTION :     divide an address (like 0xabcd) into components of page and offset as rewuired by the EEPROM
*
* INPUTS :
*       PARAMETERS:	ipAddr
*		GLOBALS:	
*
* OUTPUTS :
*		PARAMETERS: 
*       GLOBALS :	pageId, offAddr
*       RETURN :	
*
* PROCESS :
*                   [1]  Essentially div by PAGESIZE and modulo PAGESIZE
*
* NOTES :           
*/

unsigned int addrInterpret(unsigned int ipAddr) {
	
	unsigned char tp=0;
	pageId = 0;
	
	pageId = (ipAddr/PAGESIZE);
	offAddr = (ipAddr % PAGESIZE);
	
	if(pageId > 7) {
		//printf("Invalid Address Range: %d\n\r",ipAddr);
	    return ERROR;
	}
  	else {
    	pageId = pageId & 0x07;
      	pageId = pageId << 1;
      	pageId = pageId & 0x0E;
    }
  	return SUCCESS;
}

/*
* NAME :            start
*
* DESCRIPTION :     Generate I2C Start Condition
*
* INPUTS :
*       PARAMETERS:	
*		GLOBALS:	
*
* OUTPUTS :
*		PARAMETERS: 
*       GLOBALS :	
*       RETURN :	
*
* PROCESS :
*                   [1]  
*
* NOTES :           
*/

void start() {
	SDA = HIGH;
	SCL = HIGH;
	NOP;
	SDA = LOW;
	NOP;
	SCL = LOW;
}

/*
* NAME :            
*
* DESCRIPTION :     Generate I2C Stop Condition
*
* INPUTS :
*       PARAMETERS:	
*		GLOBALS:	
*
* OUTPUTS :
*		PARAMETERS: 
*       GLOBALS :	
*       RETURN :	
*
* PROCESS :
*                   [1]  
*
* NOTES :           
*/

void stop() {
    SDA = LOW;
    SCL = HIGH;
    NOP;
    SDA = HIGH;
    NOP;
    SCL = LOW;
}

/*
* NAME :            
*
* DESCRIPTION :     Generate I2C Clock
*
* INPUTS :
*       PARAMETERS:	
*		GLOBALS:	
*
* OUTPUTS :
*		PARAMETERS: 
*       GLOBALS :	
*       RETURN :	st - read bit
*
* PROCESS :
*                   [1]  
*
* NOTES :           
*/

unsigned char clock()
{
    unsigned char st = 0;
    
    SCL = HIGH;
    NOP;
    st = SDA;
    SCL = LOW;
    return (st);
}

/*
* NAME :            write
*
* DESCRIPTION :     Shift a byte onto the I2C bus and return status 
*
* INPUTS :
*       PARAMETERS:	byte
*		GLOBALS:	
*
* OUTPUTS :
*		PARAMETERS: 
*       GLOBALS :	
*       RETURN :	status
*
* PROCESS :
*                   [1]  
*
* NOTES :           
*/

unsigned char write(unsigned char byte) {
	
    unsigned char mask = 0x80;
    unsigned char status= ACK;

    for ( ; mask > 0 ;  ) {
            SDA = (byte & mask) ? ( SET ):( CLEAR ) ;
            mask >>= 1;
            clock();
    }
	status = clock();
    return status;
}

/*
* NAME :            read
*
* DESCRIPTION :     shift a byte in through I2C
*
* INPUTS :
*       PARAMETERS:	status (ACK/NACK)
*		GLOBALS:	
*
* OUTPUTS :
*		PARAMETERS: 
*       GLOBALS :	
*       RETURN :	byte
*
* PROCESS :
*                   [1] At valid SCL, read SDA into 'level' and OR it with byte
*					[2] Shift 'byte' to get ready for next bit on SDA
*					[3] At the end, send ACK or NACK as requested by caller 
*
* NOTES :           
*/

unsigned char read(unsigned char status) {
	
    unsigned char byte = 0;
    unsigned char count = 0;
    unsigned char level = 0;
    
    SDA = HIGH;  			//as input
    for (count = 0; count < 8; count++) {
	    byte <<= 1;
	    level = clock();
	    byte |= level;
    }
    SDA = status;           /* status = 1 :- NACK
                             * status = 0 :- ACK */
    clock();
    return byte;
}

/*
* NAME :            byte_write
*
* DESCRIPTION :     Complete function to write byte to  24C16 EEPROM via I2C
*
* INPUTS :
*       PARAMETERS:	addr, val
*		GLOBALS:	
*
* OUTPUTS :
*		PARAMETERS: 
*       GLOBALS :	
*       RETURN :	
*
* PROCESS :
*                   [1]  Check for valid ACKS on probing device then write
*
* NOTES :           
*/

unsigned char byte_write( unsigned int addr, unsigned char val ) {
	
	unsigned char status = ERROR;

    addrInterpret(addr);
    start();
    if ( write(EEPROM_WRITE_ADDR | pageId) == ACK ) {
		if ( write(offAddr ) == ACK ) {
			if ( write(val) == ACK ) {
				status = SUCCESS;
            }
            else {
            	printf("write val failed!!\n\r");
            }
        }
        else {
                printf("write low byte failed %x || %x!!\n\r",EEPROM_WRITE_ADDR,pageId);
        }
    }
    else {
		printf("write device id failed!!\n\r");
    }
    stop();
    return status;
}

/*
* NAME :            page_write
*
* DESCRIPTION :     Write a page at a time - 16 bytes
*
* INPUTS :
*       PARAMETERS:	addr, *str
*		GLOBALS:	
*
* OUTPUTS :
*		PARAMETERS: 
*       GLOBALS :	
*       RETURN :	
*
* PROCESS :
*                   [1]  probe device for functionality then shift multiple bytes to device using write
*
* NOTES :           
*/

unsigned char page_write(unsigned int addr, const unsigned char *str ) {
	
	unsigned char status = ERROR;
	unsigned char bytes;

	addrInterpret(addr);
	start();

    if ( write(EEPROM_WRITE_ADDR | pageId) == ACK ) {
    	if ( write(offAddr) == ACK ) {
    		for ( bytes = 0; str[bytes]; bytes++ ) {
    			if (bytes <= (15- offAddr)) {
    				if (write(str[bytes]) != ACK) {
    					printf("page write to EEPROM failed!!\n\r");
                        status = ERROR;
                        stop();
                        delay_millisec(10);
                        return status;
                    }
    			}
                else {
                    stop();
                    delay_millisec(10);
                    status = SUCCESS;
                    return status;
              	}
			} //for loop
            status = SUCCESS;
    	}//offset 
    	else {
    		printf("write low byte failed!!\n\r");
    	}
	}//pageId
    else {
    	printf("write device id failed!!\n\r");
    }

    stop();
    delay_millisec(10);
    return status;
}

/*
* NAME :            byte_read
*
* DESCRIPTION :     Complete function to read byte from 24C16 EEPROM via I2C
*
* INPUTS :
*       PARAMETERS:	addr
*		GLOBALS:	
*
* OUTPUTS :
*		PARAMETERS: 
*       GLOBALS :	
*       RETURN :	
*
* PROCESS :
*                   [1]  Check for valid ACKS on probing device then read
*
* NOTES :           
*/

unsigned char byte_read( unsigned int addr )
{
	unsigned char val = ERROR;
	offAddr=0;
	pageId=0;
	addrInterpret(addr);
	start();

    if ( write(EEPROM_WRITE_ADDR | pageId) == ACK ) {
    	if ( write(offAddr) == ACK ) {
    		start();
    		if ( write(EEPROM_READ_ADDR | pageId) == ACK) {
    			val = read(NACK);
    		}
    		else {
    			printf("read_byte: Read id failed %x %x!!!\n\r",EEPROM_READ_ADDR, pageId);
    		}
    	}
    	else {
    		printf("Read_byte: low byte failed %x %x!!!\n\r",EEPROM_READ_ADDR, pageId);
    	}
    }
    else {
    	printf("read_byte: device_write id failed!!!\n\r");
    }
    stop();
    return val;
}

/*
* NAME :            clear_eeprom
*
* DESCRIPTION :     Erase all data in EEPROM
*
* INPUTS :
*       PARAMETERS:	
*		GLOBALS:	
*
* OUTPUTS :
*		PARAMETERS: 
*       GLOBALS :	
*       RETURN :	
*
* PROCESS :
*                   [1]  Call byte_write repetitively
*
* NOTES :           Takes about half a minute to complete
*/


void clear_eeprom() {
	
	int i=0;
	
	for (i = 0; i < 2048 ; i++) {
		byte_write(i, 0xFF);
		delay_millisec(2);
	}
}

/*
* NAME :            eeprom_reset
*
* DESCRIPTION :     Reset EEPROM as per manufacturer's specifications (Microchip AN709)
*
* INPUTS :
*       PARAMETERS:	
*		GLOBALS:	
*
* OUTPUTS :
*		PARAMETERS: 
*       GLOBALS :	
*       RETURN :	
*
* PROCESS :
*                   [1] 
*
* NOTES :          	
*/

void eeprom_reset() {

	unsigned char i = 0;

	start();
	for ( i=0 ; i < 9 ; i++  ) {
		SDA = 1 ;
		clock();
	}
	start();
	stop();
}
